using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Data.SqlClient;
using System.Data;
using Microsoft.ApplicationBlocks.Data;
using System.IO;
using Microsoft.SqlServer.Dts.Runtime;
using System.Xml;
using System.Reflection;

namespace Microsoft.SharedSource.SqlServer.MDDE.Repository
{
    public sealed class MDDEPackageTemplate
    {
        #region Fields

        MDDERepository parentRepository;
        int id;
        string name;
        string body;
        PackageCollection packages = null;
        TemplateInstanceCollection templateInstances = null;
        ConfigurableComponentCollection configurableComponents = null;

        #endregion

        #region Properties

        /// <summary>
        /// Template id.
        /// </summary>
        public int Id
        {
            get { return id; }
        }

        /// <summary>
        /// Template name.
        /// </summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        /// <summary>
        /// Template body.
        /// </summary>
        public string Body
        {
            get
            {
                if (body == null)
                {
                    // Get the template body.
                    body = SqlHelper.ExecuteScalar(
                        parentRepository.RepositoryConnection,
                        CommandType.Text,
                        Resource.GetTemplateBody,
                        new SqlParameter("TemplateID", this.Id)).ToString();
                }
                return body;
            }
            set { body = value; }
        }

        /// <summary>
        /// Dependent packages.
        /// </summary>
        public PackageCollection Packages
        {
            get
            {
                if (packages == null)
                {
                    RefreshPackages();
                }
                return packages;
            }
        }

        public MDDERepository ParentRepository
        {
            get { return parentRepository; }
        }

        public TemplateInstanceCollection TemplateInstances
        {
            get
            {
                if (templateInstances == null)
                    RefreshTemplateInstances();
                return templateInstances;
            }
        }

        public ConfigurableComponentCollection ConfigurableComponents
        {
            get
            {
                if (this.configurableComponents == null)
                    this.configurableComponents =
                        new ConfigurableComponentCollection(this);
                return configurableComponents;
            }
        }

        public XmlDocument Configuration
        {
            get
            {
                // Create a configurable components xml.
                XmlDocument configComponents = new XmlDocument();
                XmlElement rootElement = configComponents.CreateElement("Template");
                XmlAttribute rootElementName = configComponents.CreateAttribute("ComponentName");
                rootElementName.Value = this.Name;
                rootElement.Attributes.Append(rootElementName);
                XmlAttribute rootElementType = configComponents.CreateAttribute("ComponentType");
                rootElementType.Value = "Template";
                rootElement.Attributes.Append(rootElementType);
                XmlAttribute rootElementUri = configComponents.CreateAttribute("ComponentUri");
                rootElementUri.Value = "Template=" + this.Name;
                rootElement.Attributes.Append(rootElementUri);

                configComponents.AppendChild(rootElement);

                // Foreach configurable template component,
                XmlElement configComponent;
                foreach (TemplateConfigurableComponent comp in this.ConfigurableComponents)
                {
                    // Create an element in the document.
                    configComponent = configComponents.CreateElement("ConfigurableComponent");

                    // Copy in all the string properties 
                    // of the component as attributes into the xml.
                    XmlAttribute compAttribute;
                    foreach (PropertyInfo compProp in comp.GetType().GetProperties())
                    {
                        if (compProp.PropertyType == typeof(string))
                        {
                            compAttribute = configComponents.CreateAttribute(
                                compProp.Name);

                            compAttribute.Value =
                                compProp.GetValue(comp, null).ToString();

                            configComponent.Attributes.Append(compAttribute);
                        }
                    }

                    // Copy in all the scalar properties.
                    foreach (TemplateScalarProperty scalarProp in comp.ScalarProperties)
                    {
                        XmlElement configProperty =
                            configComponents.CreateElement("ConfigurableProperty");

                        XmlAttribute propName =
                            configComponents.CreateAttribute("Name");
                        propName.Value = scalarProp.PropertyName;

                        configProperty.Attributes.Append(propName);

                        configComponent.AppendChild(configProperty);
                    }

                    // Append this component to root.
                    rootElement.AppendChild(configComponent);
                }

                return configComponents;
            }
        }

        #endregion

        #region Methods

        internal void RefreshPackages()
        {
            // TODO: Provide a better detached logic here that 
            // clearing of entire collection and repopulating 
            // it again.
            //
            // Do not clear the collection here since some of the
            // clients might be holding references to some of the
            // packages. Thus only add/remove changes but do not 
            // clear the entire collection.

            // Get the template instance collection.
            DataSet packageNames = SqlHelper.ExecuteDataset(
                this.parentRepository.RepositoryConnection,
                CommandType.Text,
                Resource.GetPackageNamesFromTemplateID,
                new SqlParameter("templateID", this.Id));

            if (packages == null)
                packages = new PackageCollection();

            // Pass2:   Add the packages that are in the 
            //          repository but not in the collection.
            string packageName;
            ArrayList doNotDelete = new ArrayList();
            for (int i = 0; i < packageNames.Tables[0].Rows.Count; i++)
            {
                packageName =
                    packageNames.Tables[0].Rows[i]["PackageName"].ToString();

                if (!packages.ContainsPackage(packageName))
                    packages.Add(new MDDEPackage(this, packageName));

                // Mark the package as 'Not to be deleted'.
                doNotDelete.Add(packageName);
            }

            // Pass2:   Collect the packages that are in the 
            //          collection but not in the repository.
            ArrayList toBeDeleted = new ArrayList();
            foreach (MDDEPackage pkg in packages)
                if (!doNotDelete.Contains(pkg.Name))
                    toBeDeleted.Add(pkg.Name);

            // Pass3: Finally delete all the unwanted packages.
            foreach (string str in toBeDeleted)
                packages.Remove(str);
        }

        internal void RefreshTemplateInstances()
        {
            // Get the template instance collection.
            DataSet instanceInfo = SqlHelper.ExecuteDataset(
                this.parentRepository.RepositoryConnection,
                CommandType.Text,
                Resource.GetTemplateInstances,
                new SqlParameter("templateId", this.Id));

            templateInstances = new TemplateInstanceCollection(this);

            // Add all the instances.
            string instanceName;
            for (int i = 0; i < instanceInfo.Tables[0].Rows.Count; i++)
            {
                // Create the instance.
                instanceName =
                    instanceInfo.Tables[0].
                    Rows[i]["TemplateInstanceName"].ToString();

                MDDETemplateInstance instance =
                    new MDDETemplateInstance(this, instanceName);

                // Append it to the list.
                this.templateInstances.Add(instance);
            }
        }

        public MDDEPackageTemplate(MDDERepository repository, string templateName)
        {
            this.parentRepository = repository;
            this.name = templateName;

            // Get the basic template information.
            DataSet templateInfo = SqlHelper.ExecuteDataset(
                parentRepository.RepositoryConnection,
                CommandType.Text,
                Resource.GetTemplateInfo,
                new SqlParameter("templateName", templateName));

            // We could not find the template.
            if (templateInfo.Tables[0].Rows.Count == 0)
                return;

            // We could find the template.
            this.id =
                Convert.ToInt32(templateInfo.Tables[0].Rows[0]["TemplateID"]);

            // Look for the associated configurable components.
            this.configurableComponents =
                new ConfigurableComponentCollection(this);

            DataSet configComponentInfo = SqlHelper.ExecuteDataset(
                parentRepository.RepositoryConnection,
                CommandType.Text,
                Resource.GetTemplateConfigurableComponentUris,
                new SqlParameter("templateId", this.id));

            // Add in each of the above components to internal list.
            foreach (DataRow r in configComponentInfo.Tables[0].Rows)
            {
                // Create a new component object.
                TemplateConfigurableComponent objComp =
                    new TemplateConfigurableComponent(
                    this, r["ComponentUri"].ToString());

                // Add this component to list.
                this.configurableComponents.Add(objComp);
            }
        }

        public void Create()
        {
            // Verify that we have sufficient information.
            if (string.IsNullOrEmpty(this.name) ||
                string.IsNullOrEmpty(this.body))
                throw new Exception(
                    "Name and body of a template cannot be null or empty");

            // Begin a transaction.
            SqlTransaction tn =
                this.ParentRepository.RepositoryConnection.BeginTransaction();

            try
            {
                SqlParameter[] spParams = new SqlParameter[2];
                spParams[0] = new SqlParameter("@templateName", this.Name);
                spParams[1] = new SqlParameter("@templateBody", this.Body);

                // Create a new package template.
                this.id = Convert.ToInt32(
                    SqlHelper.ExecuteScalar(tn, "sp_CreateTemplate", spParams));

                // Create the template components.
                foreach (TemplateConfigurableComponent comp
                    in this.ConfigurableComponents)
                {
                    comp.Create(tn);
                }

                // Commit the transaction.
                tn.Commit();
            }
            catch (Exception ex)
            {
                tn.Rollback();
                throw ex;
            }
            this.parentRepository.RefreshPackageTemplates();
        }

        public void Delete()
        {
            // Delete package.
            SqlHelper.ExecuteScalar(
                this.parentRepository.RepositoryConnection,
                CommandType.StoredProcedure,
                "sp_DeleteTemplate",
                new SqlParameter("templateID", this.Id));
        }

        public void Update()
        {
            // Begin a transaction.
            SqlTransaction tn =
                this.ParentRepository.RepositoryConnection.BeginTransaction();

            try
            {
                // Get the template components list.
                DataSet configComponentInfo =
                    SqlHelper.ExecuteDataset(tn,
                    CommandType.Text,
                    Resource.GetTemplateConfigurableComponentUris,
                    new SqlParameter("templateId", this.Id));

                List<string> toBeDeleted =
                    new List<string>();

                List<TemplateConfigurableComponent> toBeUpdated =
                    new List<TemplateConfigurableComponent>();

                List<TemplateConfigurableComponent> toBeInserted =
                    new List<TemplateConfigurableComponent>();

                Hashtable repositoryComponents = new Hashtable();
                foreach (DataRow r0 in configComponentInfo.Tables[0].Rows)
                {
                    repositoryComponents.Add(r0["ComponentUri"], null);

                    // Find all the template components that 
                    // are now not present in the collection 
                    // and delete them from the repository.
                    if (!this.ConfigurableComponents.
                        ContainsConfigurableComponent(
                        r0["ComponentUri"].ToString()))
                        toBeDeleted.Add(r0["ComponentUri"].ToString());
                }

                foreach (TemplateConfigurableComponent comp
                    in this.ConfigurableComponents)
                {
                    // Find all the template components that 
                    // are present in the collection 
                    // and update them in the repository.
                    if (repositoryComponents.Contains(comp.ComponentUri))
                        toBeUpdated.Add(comp);
                    // Find new members in the collection 
                    // and insert them into the repository.
                    else
                        toBeInserted.Add(comp);
                }

                // Delete the components.
                foreach (string compToBeDeleted in toBeDeleted)
                {
                    TemplateConfigurableComponent comp =
                        new TemplateConfigurableComponent(
                        this, compToBeDeleted, tn);

                    comp.Delete(tn);
                }

                // Update the components.
                foreach (TemplateConfigurableComponent comp in toBeUpdated)
                    comp.Update(tn);

                // Insert new components.
                foreach (TemplateConfigurableComponent comp in toBeInserted)
                    comp.Create(tn);

                // Commit the transaction.
                tn.Commit();
            }
            catch (Exception ex)
            {
                tn.Rollback();
                throw ex;
            }
            this.parentRepository.RefreshPackageTemplates();
        }

        #endregion
    }

    public sealed class PackageTemplateCollection : IEnumerable
    {
        #region Fields

        private Dictionary<string, MDDEPackageTemplate> templates =
            new Dictionary<string, MDDEPackageTemplate>();

        MDDERepository parent;

        #endregion

        #region Properties

        public MDDERepository Parent
        {
            get { return parent; }
            set { parent = value; }
        }

        public MDDEPackageTemplate this[string templateName]
        {
            get { return templates[templateName]; }
        }

        #endregion

        #region Methods

        internal PackageTemplateCollection(MDDERepository parent)
        {
            this.parent = parent;
        }

        public void Add(MDDEPackageTemplate template)
        {
            templates.Add(template.Name, template);
        }

        public void Remove(MDDEPackageTemplate template)
        {
            templates.Remove(template.Name);
        }

        public bool ContainsTemplate(string templateName)
        {
            return templates.ContainsKey(templateName);
        }

        #endregion

        #region IEnumerable Members

        public IEnumerator GetEnumerator()
        {
            return templates.Values.GetEnumerator();
        }

        #endregion
    }
}
